import enum
from loguru import logger
import vision_utils
from vision_utils import loopok
import time
import serial


def check_winner(board):
    # 检查当前棋盘状态是否有玩家获胜或平局
    winning_combinations = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],  # 行
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],  # 列
        [0, 4, 8],
        [2, 4, 6],  # 对角线
    ]
    for combination in winning_combinations:
        a, b, c = combination
        if board[a] and board[a] == board[b] and board[a] == board[c]:
            return board[a]
    if "" not in board:
        return "tie"
    return None


def board_data_convert(dat):
    lookup_dict = {
        (2, 0): 0,
        (1, 0): 1,
        (0, 0): 2,
        (2, 1): 3,
        (1, 1): 4,
        (0, 1): 5,
        (2, 2): 6,
        (1, 2): 7,
        (0, 2): 8,
    }
    if type(dat) == int:
        return list(lookup_dict.keys())[dat]
    else:
        return lookup_dict[dat]


def get_board_from_pieces(pieces):
    board = [""] * 9
    for piece in pieces:
        if piece["area"] == 0:
            board[board_data_convert(piece["grid"])] = (
                "O" if piece["type"] == 0 else "X"
            )
    return board


class GameStatus(enum.Enum):
    SYS_INIT = -2
    INIT = 0
    USER_ACT = 1
    BOT_ACT = -1
    ANTI_CHEAT = 2
    GAME_OVER = 3


class DecisionUtil:
    def __init__(self, driver: serial.Serial):
        self.driver = driver

    def anticheat(
        self, gamestatus, point_detail, area_rects, bot_role, perv_board=None
    ):
        currections = []

        if gamestatus == GameStatus.INIT:
            for index, piece in enumerate(point_detail):
                if piece["area"] == 0:
                    new_rect = (
                        area_rects[(-2, -2)]
                        if bot_role == piece["type"]
                        else area_rects[(-1, -1)]
                    )
                    new_place = vision_utils.find_available_place(
                        new_rect["rect"], piece["axes"], point_detail
                    )
                    assert new_place is not None
                    real_new_place = vision_utils.get_real_posi(new_place)
                    currections.append(
                        {
                            "from": vision_utils.get_real_posi(piece["center"]),
                            "to": real_new_place,
                        }
                    )

                    point_detail[index] = {
                        "center": new_place,
                        "axes": piece["axes"],
                        "type": piece["type"],
                        "rect": new_rect["rect"],
                        "grid": new_rect["grid"],
                        "area": new_rect["area"],
                        "real": real_new_place,
                    }
        elif gamestatus == GameStatus.ANTI_CHEAT:
            perv_board = get_board_from_pieces(perv_board)
            current_board = get_board_from_pieces(point_detail)
            for grid_index, grid in enumerate(perv_board):
                if grid != current_board[grid_index]:
                    if current_board[grid_index] != "":
                        for invalid_chess in filter(
                            lambda x: x[1]["grid"] == board_data_convert(grid_index),
                            enumerate(point_detail),
                        ):
                            new_rect = area_rects[
                                tuple(
                                    [-2 if invalid_chess[1]["type"] == bot_role else -1]
                                    * 2
                                )
                            ]
                            new_place = vision_utils.find_available_place(
                                new_rect["rect"],
                                invalid_chess[1]["axes"],
                                point_detail,
                            )
                            real_new_place = vision_utils.get_real_posi(new_place)
                            currections.append(
                                {
                                    "from": vision_utils.get_real_posi(
                                        invalid_chess[1]["center"]
                                    ),
                                    "to": real_new_place,
                                }
                            )
                            point_detail[invalid_chess[0]] = {
                                "center": new_place,
                                "axes": invalid_chess[1]["axes"],
                                "type": invalid_chess[1]["type"],
                                "rect": new_rect["rect"],
                                "grid": new_rect["grid"],
                                "area": new_rect["area"],
                                "real": real_new_place,
                            }

            for grid_index, grid in enumerate(perv_board):
                if grid != current_board[grid_index]:
                    if current_board[grid_index] == "":
                        available_pieces = list(
                            filter(
                                lambda x: x[1]["area"] != 0
                                and x[1]["type"] == (0 if grid == "O" else 1),
                                enumerate(point_detail),
                            )
                        )

                        if len(available_pieces) > 0:
                            currections.append(
                                {
                                    "from": vision_utils.get_real_posi(
                                        available_pieces[0][1]["center"]
                                    ),
                                    "to": vision_utils.get_real_posi(
                                        area_rects[board_data_convert(grid_index)][
                                            "rect"
                                        ][0]
                                    ),
                                }
                            )
                    else:
                        logger.error(
                            f"Impossible case: {grid_index} {grid} {current_board[grid_index]}"
                        )

        for currection in currections:
            assert loopok()
            self.do_currection(currection)

    def do_currection(self, currection):
        logger.debug(
            f"Do Cur {currection['from']} -> {currection['to']}({currection['to']})"
        )

        self.move_to(currection["from"])
        # reset()
        self.pick()

        self.move_to(currection["to"])
        # reset()
        self.place()

        self.reset()

    def place_piece(
        self,
        target_posi,
        point_detail,
        area_rects,
        bot_role=None,
        overwrite_res=None,
    ):
        for piece in point_detail:
            condition = (
                (piece["area"] == 2 and (bot_role is None or piece["type"] == bot_role))
                if overwrite_res is None
                else (
                    overwrite_res == piece["type"]
                    and (piece["area"] == 2 or piece["area"] == 1)
                )
            )
            if condition:
                self.do_currection(
                    {
                        "from": vision_utils.get_real_posi(piece["center"]),
                        "to": vision_utils.get_real_posi(
                            area_rects[
                                (
                                    board_data_convert(target_posi)
                                    if type(target_posi) == int
                                    else target_posi
                                )
                            ]["rect"][0]
                        ),
                    }
                )
                return

    def is_act_illegal(self, perv_board, curr_board):
        for i in range(9):
            if perv_board[i] != "" and perv_board[i] != curr_board[i]:
                return True

        if len("".join(curr_board)) - len("".join(perv_board)) != 1:
            if curr_board.count("") < 9:
                return True
        return False

    def wait_for_act(self):
        while loopok():
            message = self.driver.read()
            if message == b"Q":
                logger.info(f"Received {message}, Act Done")
                time.sleep(2)
                break
            time.sleep(0.2)
            logger.debug("waiting for act done")
        # input("WAIT FOR ACT DONE >>>")

    def move_to(self, posi):
        message = f"X{posi[0]},Y{posi[1]}E"
        logger.info(f"Sending {message}")
        self.driver.write(message.encode("ascii"))

        time.sleep(4)

        assert loopok()

    def place(self):
        self.driver.flushInput()
        self.driver.write("B".encode("ascii"))
        logger.info("Place")
        time.sleep(4)

        assert loopok()

    def pick(self):
        self.driver.flushInput()
        self.driver.write("A".encode("ascii"))
        logger.info("Pick")
        time.sleep(4)

        assert loopok()

    def reset(self):
        self.driver.flushInput()
        self.driver.write("R".encode("ascii"))
        logger.info("Reset")
        self.wait_for_act()

        assert loopok()

    def release_driver(self):
        self.driver.close()
